package com.lagou.edu.message.server;

import cn.hutool.core.util.StrUtil;
import com.corundumstudio.socketio.Configuration;
import com.corundumstudio.socketio.SocketConfig;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.listener.ConnectListener;
import com.corundumstudio.socketio.listener.DisconnectListener;
import com.corundumstudio.socketio.listener.ExceptionListener;
import com.corundumstudio.socketio.namespace.Namespace;
import com.corundumstudio.socketio.protocol.Packet;
import com.corundumstudio.socketio.protocol.PacketType;
import com.lagou.edu.common.jwt.JwtUtil;
import com.lagou.edu.common.string.GsonUtil;
import com.lagou.edu.message.api.dto.Message;
import com.lagou.edu.message.consts.Constants;
import com.lagou.edu.message.server.store.StoreFacotryProvider;
import com.lagou.edu.message.util.PushUtils;
import com.lagou.edu.message.util.ServerConfigUtils;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;

import java.util.*;

@Slf4j
public class PushServer {

    public static final PushServer pushServer = new PushServer();
    private Namespace pushNamespace;
    private SocketIOServer server;

    private PushServer() {
        final Configuration configuration = new Configuration();
        configuration.setStoreFactory(StoreFacotryProvider.getRedissonStoreFactory());
        configuration.setAuthorizationListener(new UserAuthorizationListener());
        configuration.setPort(ServerConfigUtils.instance.getWebSocketPort());
        configuration.setContext(ServerConfigUtils.instance.getWebSocketContext());
        configuration.setOrigin(ServerConfigUtils.instance.getWebSocketOrigin());

        final SocketConfig socketConfig = new SocketConfig();
        socketConfig.setReuseAddress(true);
        configuration.setSocketConfig(socketConfig);

        server = new SocketIOServer(configuration);
        pushNamespace = (Namespace) server.addNamespace(ServerConfigUtils.instance.getWebSocketContext());

        configuration.setExceptionListener(new ExceptionListener() {
            @Override
            public void onEventException(Exception e, List<Object> list, SocketIOClient socketIOClient) {

                UUID sessionId = socketIOClient.getSessionId();
                log.error("onEventException, sessionId:{},roomList:{}", sessionId, socketIOClient.get(Constants.ROOM), e);
            }

            @Override
            public void onDisconnectException(Exception e, SocketIOClient socketIOClient) {

                UUID sessionId = socketIOClient.getSessionId();
                log.error("onDisconnectException, sessionId:{},roomList:{}", sessionId, socketIOClient.get(Constants.ROOM), e);
            }

            @Override
            public void onConnectException(Exception e, SocketIOClient socketIOClient) {
                UUID sessionId = socketIOClient.getSessionId();
                log.error("onConnectException, sessionId:{},roomList:{}", sessionId, socketIOClient.get(Constants.ROOM), e);
            }

            @Override
            public void onPingException(Exception e, SocketIOClient socketIOClient) {
                UUID sessionId = socketIOClient.getSessionId();
                log.error("onPingException, sessionId:{},roomList:{}", sessionId, socketIOClient.get(Constants.ROOM), e);
            }

            @Override
            public boolean exceptionCaught(ChannelHandlerContext channelHandlerContext, Throwable throwable) throws Exception {
                return false;
            }
        });

        pushNamespace.addConnectListener(new ConnectListener() {
            @Override
            public void onConnect(SocketIOClient socketIOClient) {
                try {
                    UUID sid = socketIOClient.getSessionId();
                    JwtUtil.JwtResult user = UserAuthorizationListener.getUserInfo(socketIOClient.getHandshakeData());

                    if(null != user) {
                        Set<String> rooms = socketIOClient.getAllRooms();

                        for (String room : rooms) {
                            socketIOClient.leaveRoom(room);
                        }

                        String userId = Integer.toString(user.getUserId());

                        List<String> roomList = PushUtils.getRoomList(userId, null, null);
                        for (String roomStr : roomList) {
                            socketIOClient.joinRoom(roomStr);
                        }

                        socketIOClient.set(Constants.ROOM, roomList);
                    }else {
                        socketIOClient.disconnect();
                        log.warn("sid:{} has no userId", sid);
                    }

                }catch (Exception e) {
                    log.error("addConnectListener-err", e);
                }
            }
        });

        pushNamespace.addDisconnectListener(new DisconnectListener() {
            @Override
            public void onDisconnect(SocketIOClient socketIOClient) {
                UUID sid = socketIOClient.getSessionId();
                JwtUtil.JwtResult user = UserAuthorizationListener.getUserInfo(socketIOClient.getHandshakeData());

                if(null != user) {
                    log.info("disconnet userId:{} userName:{} sid: {}", user.getUserId(), user.getUserName(), sid);


                }else {
                    socketIOClient.disconnect();
                    log.info("client get handShakeData is null, disconnet, sid:{}", sid);
                    return;
                }

                String userId = Integer.toString(user.getUserId());
                List<String> roomList = PushUtils.getRoomList(userId, null, null);

                for (String roomStr : roomList) {
                    socketIOClient.leaveRoom(roomStr);
                }


                try {
                    List<String> oldRoomList = socketIOClient.get(Constants.ROOM);

                    if(null != oldRoomList) {
                        for (String roomStr : oldRoomList) {
                            if(StrUtil.isBlank(roomStr)) {
                                continue;
                            }

                            socketIOClient.leaveRoom(roomStr);
                        }
                    }

                    socketIOClient.disconnect();

                }catch (Exception e) {
                    log.error("leave old room exception, sid:{}", sid, e);
                } finally {
                    try {
                        socketIOClient.del(Constants.ROOM);
                        log.error("sid:{}, del hash success,field:{}", sid, Constants.ROOM);
                    }catch (Exception e) {

                    }
                }
            }
        });
    }

    /**
     * 推送消息
     *
     * @param message
     */
    public void push(Message message) {
        final String type;
        final Integer userId;
        final String json;
        final long l2;
        try {
            long l1 = System.currentTimeMillis();

            type = message.getType();
            userId = message.getUserId();
            json = GsonUtil.toJson(message);
            l2 = System.currentTimeMillis();

            if(l2 - l1 > 0l) {
                log.info("当前node耗时：{}", l2 - l1);
            }
        }finally {

        }

        String room;
        long l3;

        try {
            if(userId == null) {
                throw new NullPointerException("用户ID不能为空");
            }

            room = PushUtils.getRoom(null, userId, null);
            log.info("send message to {}, type:{}", room, type);
            l3 = System.currentTimeMillis();
        }finally {

        }

        Packet packet;
        long l4;

        try {
            packet = new Packet(PacketType.MESSAGE);
            packet.setSubType(PacketType.EVENT);
            packet.setName("message");
            ArrayList<Object> data = new ArrayList<>();
            data.add(json);
            packet.setData(data);
            packet.setNsp(pushNamespace.getName());

            l4 = System.currentTimeMillis();
            if(l4 - l3 > 0l) {
                log.info("当前node,packet耗时：room={}, time={}", room, l4 - l3);
            }



        }finally {

        }

        int i1;
        final long l5;

        try {
            i1 = 0;
            Iterable<SocketIOClient> clients = pushNamespace.getRoomClients(room);

            for (SocketIOClient client : clients) {
                client.send(packet);
                i1++;
            }

            l5 = System.currentTimeMillis();
            if(l5 - l4 > 0l) {
                log.info("当前node,push耗时：room={}, time={}", room, l4 - l3);
            }


        }catch (Exception e) {
            log.error("push消息失败", e);
        }finally {

        }

    }

    /**
     * 同步启动服务；
     */
    public void start() {
        try {
            server.start();
        } catch (Exception e) {
            log.error("Push server start failed!", e);
            System.exit(-1);
        }
    }

    /**
     * 停止服务
     */
    public void stop() {
        server.stop();
    }

    /**
     * 获取在线的所有用户
     * @return
     */
    public List<Integer> getOnlineUsers() {
        List<Integer> userIds = new ArrayList<>();
        for (SocketIOClient client : pushNamespace.getAllClients()) {
            JwtUtil.JwtResult user = UserAuthorizationListener.getUserInfo(client.getHandshakeData());

            if(null != user) {
                userIds.add(user.getUserId());
            }
        }

        return userIds;

    }

    public Map<String, Object> getStatus() {
        HashMap<String, Object> status = new HashMap<>();
        status.put("namespace", pushNamespace.getName());   // namespace
        status.put("rooms", pushNamespace.getRooms());
        status.put("clients", pushNamespace.getAllClients().size());

        return status;
    }

}
